#include "EventPool.h"
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/timerfd.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>

using std::vector;
using std::list;
using std::placeholders::_1;
using std::placeholders::_2;

EventPool::EventPool()
    : epollfd_(epoll_create1(EPOLL_CLOEXEC)),
      eventfd_(eventfd(0, EFD_CLOEXEC)),
      loop_(false),
      cond_(mutex_)
{
    assert(epollfd_ >= 0);
    assert(eventfd_ >= 0);
    AddFd(eventfd_, EPOLLIN, std::bind(&EventPool::EventHandler, this, _1, _2));
}

EventPool::~EventPool()
{
    if (epollfd_ >= 0)
        close(epollfd_);
    if (eventfd_ >= 0)
        close(eventfd_);
}

void EventPool::Start(int nums)
{
    {
        MutexGruad lock(mutex_);
        if (loop_)
            return;
        loop_ = true;
        threads_.resize(nums);
        for (int i = 0; i < nums; i++) {
            int err = pthread_create(threads_.data() + i, NULL, EventPool::FunctionThread, this);
            assert(err == 0); err;
        }
    }
    vector<struct epoll_event> events(4);
    while (loop_) {
        int result = epoll_wait(epollfd_, events.data(), events.size(), -1);
        if (!loop_) {
            break;
        }
        if (result <= 0) {
            if (errno == EINTR) {
                continue;
            } else {
                abort();
            }
        }
        list<Functor> cbs;
        {
            MutexGruad lock(fdMutex_);
            for (int i = 0; i < result; i++) {
                int fd = events[i].data.fd;
                int event = events[i].events;
                auto iter = IOfuctors_.find(fd);
                if (iter != IOfuctors_.end()) {
                    cbs.emplace_back(std::bind(iter->second, fd, event));
                }
            }
        }
        Run(std::move(cbs));
    }
    for (int i = 0; i < nums; i++) {
        pthread_join(threads_[i], NULL);
    }
}

void EventPool::WakeUp()
{
    uint64_t wake = 1;
    write(eventfd_, &wake, sizeof(uint64_t));
}

void EventPool::EventHandler(int fd, int events)
{
    assert(fd == eventfd_);
    assert(events == EPOLLIN);
    uint64_t result;
    read(eventfd_, &result, sizeof(uint64_t));
}

void EventPool::Stop()
{
    MutexGruad lock(mutex_);
    loop_ = false;
    cond_.SignalAll();
    WakeUp();
}

void EventPool::Run(Functor cb)
{
    MutexGruad lock(mutex_);
    functors_.push_back(std::move(cb));
    cond_.Signal();
}

void EventPool::Run(std::list<Functor> cbs)
{
    MutexGruad lock(mutex_);
    functors_.splice(functors_.end(), std::move(cbs));
    cond_.SignalAll();
}

void* EventPool::FunctionThread(void *ctx)
{
    EventPool *ctx_ = static_cast<EventPool *>(ctx);

    while (ctx_->loop_) {
        Functor cb;
        {
            MutexGruad lock(ctx_->mutex_);
            while (1) {
                if (!ctx_->loop_) {
                    break;
                }
                if (ctx_->functors_.size()) {
                    cb = std::move(ctx_->functors_.front());
                    ctx_->functors_.pop_front();
                    break;
                } else {
                    ctx_->cond_.Wait();
                }
            }
        }
        if (cb) {
            cb();
        }
    }

    return NULL;
}

int EventPool::AddFd(int fd, int events, IO_Functor iocb)
{
    MutexGruad lock(fdMutex_);
    if (IOfuctors_.find(fd) != IOfuctors_.end())
        return -1;
    struct epoll_event event;
    event.events = events | EPOLLET;
    event.data.fd = fd;
    int err = epoll_ctl(epollfd_, EPOLL_CTL_ADD, fd, &event);
    if (err == 0) {
        IOfuctors_[fd] = std::move(iocb);
    }
    return err;
}

int EventPool::ModFd(int fd, int events, IO_Functor iocb)
{
    MutexGruad lock(fdMutex_);
    if (IOfuctors_.find(fd) == IOfuctors_.end())
        return -1;
    struct epoll_event event;
    event.events = events | EPOLLET;
    event.data.fd = fd;
    int err = epoll_ctl(epollfd_, EPOLL_CTL_MOD, fd, &event);
    if (err == 0) {
        IOfuctors_[fd] = std::move(iocb);
    }
    return err;
}

void EventPool::DelFd(int fd)
{
    MutexGruad lock(fdMutex_);
    auto iter = IOfuctors_.find(fd);
    if (iter == IOfuctors_.end())
        return;
    epoll_ctl(epollfd_, EPOLL_CTL_DEL, fd, NULL);
    IOfuctors_.erase(iter);
}
